<?php

/*
 * Copyright 2008 the original author or authors.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#namespace tehframework\request;

#use \InvalidArgumentException;
#use \OutOfRangeException;
#use \BadMethodCallException;

abstract class Request implements RequestParametersHolder
{
	const METHOD_CREATE = 'CREATE';
	const METHOD_READ   = 'READ';
	const METHOD_WRITE  = 'WRITE';
	const METHOD_REMOVE = 'REMOVE';
	
	/**
	 * @var string?
	 */
	private $lock;
	
	/**
	 * @var Request::METHOD_*
	 */
	private $method;
	
	/**
	 * @param  Request $request
	 * @return Request
	 */
	abstract public function merge(Request $request);
	
	/**
	 * @param  string? $key
	 * @return string?
	 * @throws InvalidArgumentException
	 */
	public function toggleLock($key = null)
	{
		if ($key === null)
		{
			return $this->lock = md5(uniqid(microtime()));
		}
		elseif ($key === $this->lock)
		{
			return $this->lock = null;
		}
		else
		{
			throw new InvalidArgumentException(
				'Cannot unlock the request data, because invalid key was specified.'
			);
		}
	}
	
	/**
	 * @return Request::METHOD_*
	 * @throws BadMethodCallException
	 */
	public function getMethod()
	{
		if ($this->method === null)
		{
			throw new BadMethodCallException(
				'Can not return the request method, because it was not set yet.'
			);
		}
		
		return $this->method;
	}
	
	/**
	 * @param  string $method
	 * @return WebRequest
	 * @throws BadMethodCallException
	 */
	public function setMethod($method)
	{
		$this->assertIsLocked();
		
		$this->setRequestMethod($method);
		
		return $this;
	}
	
	/**
	 * @param  string $name
	 * @return bool
	 */
	public function hasParameter($name)
	{
		return array_key_exists($name, $this->parameters);
	}
	
	/**
	 * @param  string $name
	 * @return mixed
	 * @throws OutOfRangeException
	 */
	public function getParameter($name)
	{
		if (!array_key_exists($name, $this->parameters))
		{
			throw new OutOfRangeException(sprintf(
				'Can not return [%s] parameter, because it does not exist.', $name
			));
		}
		
		return $this->parameters[$name];
	}
	
	/**
	 * @return array<string,mixed>
	 */
	public function getParameters()
	{
		return $this->parameters;
	}
	
	/**
	 * @param  string $name
	 * @param  mixed? $default
	 * @return mixed
	 */
	public function tryGetParameter($name, $default = null)
	{
		return array_key_exists($name, $this->parameters)
			? $this->parameters[$name] : $default;
	}
	
	/**
	 * @param  string $name
	 * @param  mixed $value
	 * @return Request
	 * @throws BadMethodCallException
	 */
	public function setParameter($name, $value)
	{
		$this->assertIsLocked();
		
		$this->parameters[$name] = $value;
		
		return $this;
	}
	
	/**
	 * @param  array<string,mixed> $parameters
	 * @return Request
	 * @throws BadMethodCallException
	 */
	public function setParameters(array $parameters)
	{
		$this->assertIsLocked();
		
		$this->parameters = $parameters;
		
		return $this;
	}
	
	/**
	 * @param  string $name
	 * @return Request
	 * @throws OutOfRangeException
	 * @throws BadMethodCallException
	 */
	public function removeParameter($name)
	{
		$this->assertIsLocked();
		
		if (!array_key_exists($name, $this->parameters))
		{
			throw new OutOfRangeException(sprintf(
				'Can not remove [%s] parameter, because it does not exist.', $name
			));
		}
		
		unset($this->parameters[$name]);
		
		return $this;
	}
	
	/**
	 * @return Request
	 * @throws BadMethodCallException
	 */
	public function clearParameters()
	{
		$this->assertIsLocked();
		
		$this->parameters = array();
		
		return $this;
	}
	
	/**
	 * @param  RequestParametersHolder $parametersHolder
	 * @return Request
	 * @throws BadMethodCallException
	 */
	public function mergeParameters(RequestParametersHolder $parametersHolder)
	{
		$this->assertIsLocked();
		
		$this->parameters =
			array_merge($this->parameters, $parametersHolder->getParameters());
		
		return $this;
	}
	
	/**
	 * @param  string $requestMethod
	 * @return Request
	 * @throws InvalidArgumentException
	 */
	protected function setRequestMethod($requestMethod)
	{
		switch ($requestMethod)
		{
			case self::METHOD_CREATE:
			case self::METHOD_READ:
			case self::METHOD_REMOVE:
			case self::METHOD_WRITE:
				$this->requestMethod = $requestMethod;
				break;
			
			default:
				throw new InvalidArgumentException(sprintf(
					'[%s] is not valid request method.', $requestMethod
				));
		}
		
		return $this;
	}
	
	/**
	 * @return bool
	 */
	protected function isLocked()
	{
		return $this->lock !== null;
	}
	
	/**
	 * @throws BadMethodCallException
	 */
	protected function assertIsLocked()
	{
		if ($this->isLocked())
		{
			throw new BadMethodCallException('Request data is closed for changes.');
		}
	}
}